{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 355,
   "id": "8a62936b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "import warnings\n",
    "import requests\n",
    "import re\n",
    "import pandas as pd\n",
    "from bs4 import BeautifulSoup as bs\n",
    "import datetime\n",
    "import json\n",
    "import time\n",
    "from abc import ABC, abstractmethod\n",
    "from dataclasses import dataclass\n",
    "from collections import namedtuple, defaultdict\n",
    "from typing import Optional, Any, Union, List\n",
    "from copy import deepcopy"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4910b061",
   "metadata": {},
   "source": [
    "#### Задача данного иследования - собрать инфу о дополнительныйх параметрах, разобраться с сущностями, доступных каждым эндпоинтом и написать базовые функции проверки параметров (валидации). По результату накатаем часть функционала нашей либы."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "152c4a0f",
   "metadata": {},
   "source": [
    "#### Подтянем результаты и классы из ноута с изучением api"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6286e3ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "root_url = \"http://iss.moex.com\"\n",
    "docs_url = \"/iss/reference\"\n",
    "save_path = 'moex_api_response'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b6271b1c",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class MoexApi:\n",
    "    url_faq: str\n",
    "    url_template: str\n",
    "    description: str = ''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 269,
   "id": "4ab6107e",
   "metadata": {},
   "outputs": [],
   "source": [
    "period = ['yearly', 'monthly', 'daily']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "80722750",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Globals:\n",
    "    __instance = None\n",
    "    globals_url = \"https://iss.moex.com/iss/index.json\"\n",
    "    url_index_id = \"https://iss.moex.com/iss/statistics/engines/stock/markets/index/analytics.json\"\n",
    "    \n",
    "    base_attr = namedtuple(\"base_attr\", \"main description\")\n",
    "    \n",
    "    main_attribute = {\n",
    "        \"engines\": base_attr(\"name\", \"title\"),\n",
    "        \"markets\": base_attr(\"market_name\", \"market_title\"),\n",
    "        \"boards\": base_attr(\"boardid\", \"board_title\"),\n",
    "        \"boardgroups\": base_attr(\"name\", \"title\"),\n",
    "        \"durations\": base_attr(\"interval\", \"title\"),\n",
    "        \"securitytypes\": base_attr(\"security_type_name\", \"security_type_title\"),\n",
    "        \"securitygroups\": base_attr(\"name\", \"title\"),\n",
    "        \"securitycollections\": base_attr(\"name\", \"title\"),\n",
    "    }\n",
    "    datatypes = [\"securities\", \"trades\"]\n",
    "    \n",
    "    report_names = pd.DataFrame(\n",
    "        data=[\n",
    "            [\n",
    "                \"numtrades\", \n",
    "                \"Информация о количестве договоров по инструментам, \"\n",
    "                \"являющимся производными финансовыми инструментами (по валютным парам)\",\n",
    "            ],\n",
    "            [\n",
    "                \"participants\",\n",
    "                \"Информация о количестве лиц, имеющих открытые позиции по инструментам, \"\n",
    "                \"являющимся производными финансовыми инструментами (по валютным парам)\",\n",
    "            ],\n",
    "            [\n",
    "                \"openpositions\",\n",
    "                \"Информация об открытых позициях по инструментам, являющимся производными \"\n",
    "                \"финансовыми инструментами (по валютным парам)\",\n",
    "            ],\n",
    "            [\n",
    "                \"expirationparticipants\",\n",
    "                \"Информация о количестве лиц, имеющих открытые позиции по договорам, \"\n",
    "                \"являющимся производными финансовыми инструментами (по срокам экспирации)\",\n",
    "            ],\n",
    "            [\n",
    "                \"expirationopenpositions\",\n",
    "                \"Информация об объеме открытых позиций по договорам, являющимся производными \"\n",
    "                \"финансовыми инструментами (по срокам экспирации)\",\n",
    "            ],\n",
    "        ],\n",
    "        columns=['report_name', 'decription']\n",
    "    )\n",
    "    report_names.set_index('report_name', inplace=True)\n",
    "    \n",
    "    sessions = pd.DataFrame(\n",
    "        data=[\n",
    "            [1, \"Основная сессия\"], [2, \"Вечерняя сессия\"], [3, \"Итого (все сессии)\"], [0, \"Утренняя сессия\"]\n",
    "        ], \n",
    "        columns=['sessions', 'decription']\n",
    "    )\n",
    "    sessions.set_index('sessions', inplace=True)\n",
    "    \n",
    "    \n",
    "    def __new__(cls, *args, **kwargs):\n",
    "        if not cls.__instance:                \n",
    "            cls.__instance = super().__new__(cls, *args, **kwargs)\n",
    "        return cls.__instance\n",
    "    \n",
    "    def __init__(self):\n",
    "        moex_dict = requests.get(self.globals_url).json()\n",
    "        for entity, columns in self.main_attribute.items():\n",
    "            setattr(self, entity, pd.DataFrame(\n",
    "                data=moex_dict[entity]['data'],\n",
    "                columns=moex_dict[entity]['columns'],\n",
    "                ).set_index(columns.main)\n",
    "            )\n",
    "        self.indexids = self.get_index_id()\n",
    "    \n",
    "    def get_index_id(self):\n",
    "        index_ids = requests.get(self.url_index_id).json()\n",
    "        df_index_ids = pd.DataFrame(\n",
    "            data=index_ids['indices']['data'], columns=index_ids['indices']['columns']\n",
    "        )\n",
    "        df_index_ids.set_index('indexid', inplace=True)\n",
    "        return df_index_ids\n",
    "    \n",
    "    \n",
    "    def description(self, entity: str, name):\n",
    "        entities = entity + \"s\"\n",
    "        use_entity = getattr(self, entities, None)\n",
    "        if use_entity is None:\n",
    "            raise NameError(f\"Entity '{entity}' is not found!\")\n",
    "        if not isinstance(use_entity, pd.DataFrame):\n",
    "            raise ValueError(f\"Entity '{entity}' hasn't description\")\n",
    "        return getattr(self, entity + \"s\").loc[name, (self.main_attribute[entities].description,)]\n",
    "            "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "d5799e48",
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('api.pickle', 'rb') as file:\n",
    "    data = pickle.load(file)\n",
    "    \n",
    "OK_API = data['use_api']  # API, доступные без подписки (FYI)\n",
    "GLOBAL_GUID = data['globals']  # Глобальный справочник сущностей (FYI)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "35a781cf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "      <th>board_group_id</th>\n",
       "      <th>engine_id</th>\n",
       "      <th>market_id</th>\n",
       "      <th>board_title</th>\n",
       "      <th>is_traded</th>\n",
       "      <th>has_candles</th>\n",
       "      <th>is_primary</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>boardid</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>TQIF</th>\n",
       "      <td>177</td>\n",
       "      <td>57</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>Т+: Паи - безадрес.</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TQTF</th>\n",
       "      <td>178</td>\n",
       "      <td>57</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>Т+: ETF - безадрес.</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TQBR</th>\n",
       "      <td>129</td>\n",
       "      <td>57</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>Т+: Акции и ДР - безадрес.</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TQBS</th>\n",
       "      <td>130</td>\n",
       "      <td>57</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>Т+: А2-Акции и паи - безадрес.</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TQNL</th>\n",
       "      <td>131</td>\n",
       "      <td>57</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>Т+: Б-Акции и паи - безадрес.</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "          id  board_group_id  engine_id  market_id  \\\n",
       "boardid                                              \n",
       "TQIF     177              57          1          1   \n",
       "TQTF     178              57          1          1   \n",
       "TQBR     129              57          1          1   \n",
       "TQBS     130              57          1          1   \n",
       "TQNL     131              57          1          1   \n",
       "\n",
       "                            board_title  is_traded  has_candles  is_primary  \n",
       "boardid                                                                      \n",
       "TQIF                Т+: Паи - безадрес.          1            1           1  \n",
       "TQTF                Т+: ETF - безадрес.          1            1           1  \n",
       "TQBR         Т+: Акции и ДР - безадрес.          1            1           1  \n",
       "TQBS     Т+: А2-Акции и паи - безадрес.          0            1           1  \n",
       "TQNL      Т+: Б-Акции и паи - безадрес.          0            1           1  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(5, MoexApi(url_faq='http://iss.moex.com/iss/reference/5', url_template='http://iss.moex.com/iss/securities', description='Список бумаг торгуемых на московской бирже.')), (24, MoexApi(url_faq='http://iss.moex.com/iss/reference/24', url_template='http://iss.moex.com/iss/turnovers', description='Получить сводные обороты по рынкам.Например: https://iss.moex.com/iss/turnovers.xml')), (100, MoexApi(url_faq='http://iss.moex.com/iss/reference/100', url_template='http://iss.moex.com/iss/turnovers/columns', description='Получить описание полей для запросов оборотов по рынку/торговой системе.\\rНапример: https://iss.moex.com/iss/engines/stock/turnovers/columns.xml')), (28, MoexApi(url_faq='http://iss.moex.com/iss/reference/28', url_template='http://iss.moex.com/iss/index', description='Получить глобальные справочники ISS. \\rНапример: https://iss.moex.com/iss/index.xml')), (40, MoexApi(url_faq='http://iss.moex.com/iss/reference/40', url_template='http://iss.moex.com/iss/engines', description='Получить доступные торговые системы. Например: https://iss.moex.com/iss/engines.xml'))]\n"
     ]
    }
   ],
   "source": [
    "display(GLOBAL_GUID.boards.head())\n",
    "print(list(OK_API.items())[:5])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "79a5b6a9",
   "metadata": {},
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class ApiParams:\n",
    "    key: str\n",
    "    description: Optional[str] = None\n",
    "    default: Optional[str] = None\n",
    "    type_: Optional[str] = None"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76813a52",
   "metadata": {},
   "source": [
    "## Соберем все параметры запроса к апи:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 244,
   "id": "2016cd90",
   "metadata": {},
   "outputs": [],
   "source": [
    "API_PARAMS = {}\n",
    "param_key = None\n",
    "for api_id, api in OK_API.items():\n",
    "    API_PARAMS[api_id] = {}\n",
    "    req = requests.get(api.url_faq)\n",
    "    soup = bs(req.content)\n",
    "    \n",
    "    for data in soup.body.dl:\n",
    "        if data.name is None:\n",
    "            continue\n",
    "\n",
    "        if data.name == 'dt':\n",
    "            data_entity = data.get_text()\n",
    "            API_PARAMS[api_id][data_entity] = {}\n",
    "            continue\n",
    "        \n",
    "        if data.dl is None:\n",
    "            continue\n",
    "        \n",
    "        API_PARAMS[api_id][data_entity]['description'] = data.find('pre').get_text()\n",
    "\n",
    "        for params in data.dl:\n",
    "            if params.name == 'dt':\n",
    "                if param_key is None:\n",
    "                    param_key = params.get_text()\n",
    "                    continue\n",
    "                else:\n",
    "                    raise ValueError(\"BAD ERROR\")\n",
    "                    \n",
    "            if params.name == 'dd':\n",
    "                description = params.find('pre').get_text()\n",
    "                default, type_ = params.find_all('strong')\n",
    "\n",
    "                API_PARAMS[api_id][data_entity].setdefault('params', []).append(\n",
    "                    ApiParams(\n",
    "                        key = param_key,\n",
    "                        description = description,\n",
    "                        default = default.next.next.text,\n",
    "                        type_ = type_.next.next.text,\n",
    "                    )\n",
    "                )\n",
    "                param_key = None\n",
    "\n",
    "    time.sleep(.2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 245,
   "id": "1aff38b3",
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[(5,\n",
       "  {'securities (GET)': {'description': 'Список бумаг торгуемых на московской бирже.',\n",
       "    'params': [ApiParams(key='q', description='Поиск инструмента по части Кода, Названию, ISIN, Идентификатору Эмитента, Номеру гос.регистрации.\\nНапример: https://iss.moex.com/iss/securities.xml?q=MOEX\\nСлова длиной менее трёх букв игнорируются. Если параметром передано два слова через пробел. То каждое должно быть длиной не менее трёх букв.', default='', type_='var'),\n",
       "     ApiParams(key='lang', description='Язык результата: ru или en', default='ru', type_='var'),\n",
       "     ApiParams(key='engine', description='', default='', type_='var'),\n",
       "     ApiParams(key='is_trading', description='', default='', type_='var'),\n",
       "     ApiParams(key='market', description='', default='', type_='var'),\n",
       "     ApiParams(key='group_by', description='Группировать выводимый результат по полю. Доступны значения group и type.', default='', type_='var'),\n",
       "     ApiParams(key='limit', description='Количество выводимых инструментов (5, 10, 20,100)', default='100', type_='var'),\n",
       "     ApiParams(key='group_by_filter', description='Фильтровать по типам group или type.\\nЗависит от значения фильтра group_by', default='', type_='var'),\n",
       "     ApiParams(key='start', description='Номер строки (отсчет с нуля), с которой следует начать порцию возвращаемых данных (см. рук-во разработчика).\\r\\nПолучение ответа без данных означает, что указанное значение превышает число строк, возвращаемых запросом.', default='0', type_='number')]}}),\n",
       " (24,\n",
       "  {'turnovers (GET)': {'description': 'Обороты рынков',\n",
       "    'params': [ApiParams(key='lang', description='Язык результата: ru или en', default='ru', type_='var'),\n",
       "     ApiParams(key='is_tonight_session', description='Показывать обороты за вечернюю сессию', default='0', type_='var'),\n",
       "     ApiParams(key='date', description='', default='today', type_='var')]},\n",
       "   'turnoversprevdate (GET)': {'description': 'Обороты рынков за предыдущий торговый день',\n",
       "    'params': [ApiParams(key='lang', description='', default='ru', type_='var'),\n",
       "     ApiParams(key='is_tonight_session', description='Показывать обороты за вечернюю сессию', default='0', type_='var'),\n",
       "     ApiParams(key='date', description='', default='today', type_='var')]}}),\n",
       " (100,\n",
       "  {'turnovers (GET)': {'description': 'Справочник полей таблицы с оборотами',\n",
       "    'params': [ApiParams(key='lang', description='Язык результата: ru или en', default='ru', type_='var')]}})]"
      ]
     },
     "execution_count": 245,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(len(API_PARAMS) == len(OK_API))\n",
    "list(API_PARAMS.items())[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d0f3c81",
   "metadata": {},
   "source": [
    "#### Убедимся что все ок, почистим данные, отберем уникальные параметры\n",
    "\n",
    "1. Удалим все (GET) из ключа возвращаемой сущности\n",
    "2. Проверим глазками (выборочно) поинты не принимающие уточняющие параметры\n",
    "3. Отберем уникальные ключи параметров запроса"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 251,
   "id": "b76172da",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "5 securities\n"
     ]
    }
   ],
   "source": [
    "# 1. rename data\n",
    "for key, data in API_PARAMS.items():\n",
    "    for data_type in list(data.keys()):\n",
    "        if data_type.find(' (GET)') == -1:\n",
    "            print(key, data_type)\n",
    "        else:\n",
    "            data[data_type.replace(\" (GET)\", \"\")] = data.pop(data_type)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 256,
   "id": "30799d9b",
   "metadata": {
    "collapsed": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "http://iss.moex.com/iss/reference/843\n",
      "analytics.columns\n",
      "http://iss.moex.com/iss/reference/123\n",
      "changeover\n",
      "http://iss.moex.com/iss/reference/172\n",
      "coefficients.dates\n",
      "http://iss.moex.com/iss/reference/168\n",
      "cbrf\n",
      "http://iss.moex.com/iss/reference/758\n",
      "splits\n",
      "http://iss.moex.com/iss/reference/165\n",
      "data.dates\n",
      "http://iss.moex.com/iss/reference/166\n",
      "data.dates\n",
      "http://iss.moex.com/iss/reference/169\n",
      "dates\n",
      "http://iss.moex.com/iss/reference/134\n",
      "securities.dates\n",
      "http://iss.moex.com/iss/reference/171\n",
      "quotedsecurities.dates\n",
      "http://iss.moex.com/iss/reference/933\n",
      "monthend_accints.index\n",
      "http://iss.moex.com/iss/reference/195\n",
      "aggregates.dates\n",
      "http://iss.moex.com/iss/reference/159\n",
      "issuecapitalization\n",
      "http://iss.moex.com/iss/reference/161\n",
      "boards\n",
      "http://iss.moex.com/iss/reference/711\n",
      "securities.dates\n",
      "http://iss.moex.com/iss/reference/716\n",
      "history.dates\n",
      "http://iss.moex.com/iss/reference/214\n",
      "agregates.dates\n",
      "http://iss.moex.com/iss/reference/634\n",
      "params.dates\n",
      "http://iss.moex.com/iss/reference/41\n",
      "timetable\n",
      "http://iss.moex.com/iss/reference/33\n",
      "dataversion\n",
      "http://iss.moex.com/iss/reference/52\n",
      "dataversion\n",
      "http://iss.moex.com/iss/reference/55\n",
      "dataversion\n",
      "http://iss.moex.com/iss/reference/35\n",
      "dataversion\n",
      "http://iss.moex.com/iss/reference/32\n",
      "dataversion\n",
      "http://iss.moex.com/iss/reference/53\n",
      "dataversion\n",
      "http://iss.moex.com/iss/reference/56\n",
      "dataversion\n",
      "http://iss.moex.com/iss/reference/156\n",
      "borders\n",
      "http://iss.moex.com/iss/reference/48\n",
      "borders\n",
      "http://iss.moex.com/iss/reference/34\n",
      "dataversion\n",
      "http://iss.moex.com/iss/reference/114\n",
      "years\n",
      "http://iss.moex.com/iss/reference/115\n",
      "months\n",
      "http://iss.moex.com/iss/reference/877\n",
      "expirations\n",
      "http://iss.moex.com/iss/reference/192\n",
      "content\n",
      "http://iss.moex.com/iss/reference/194\n",
      "content\n",
      "http://iss.moex.com/iss/reference/147\n",
      "analytics.dates\n",
      "http://iss.moex.com/iss/reference/809\n",
      "futoi.dates\n",
      "http://iss.moex.com/iss/reference/715\n",
      "history.dates\n",
      "http://iss.moex.com/iss/reference/712\n",
      "securities.dates\n"
     ]
    }
   ],
   "source": [
    "# 2. find all data_type without params\n",
    "for key, data in API_PARAMS.items():\n",
    "    for data_type, value in data.items():\n",
    "        if value.get('params') is None:\n",
    "            print(OK_API[key].url_faq)\n",
    "            print(data_type)\n",
    "            break"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1704ca3",
   "metadata": {},
   "source": [
    "### Проверил, все ок!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 260,
   "id": "36d4d858",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'number', 'var', 'string'}\n"
     ]
    }
   ],
   "source": [
    "UNIQUE_KEY = {}\n",
    "types_var = set()\n",
    "for data in API_PARAMS.values():\n",
    "    for data_type in data.values():\n",
    "        for param in data_type.get('params', []):\n",
    "            name = param.key\n",
    "            types_var.add(param.type_)\n",
    "            UNIQUE_KEY.setdefault(name, {\"description\": set(), \"default\": set()})\n",
    "            UNIQUE_KEY[name][\"description\"].add(param.description)\n",
    "            UNIQUE_KEY[name][\"default\"].add(param.default)\n",
    "print(types_var)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 265,
   "id": "1308cf74",
   "metadata": {
    "collapsed": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'q': {'description': {'Поиск инструмента по части Кода, Названию, ISIN, Идентификатору Эмитента, Номеру гос.регистрации.\\nНапример: https://iss.moex.com/iss/securities.xml?q=MOEX\\nСлова длиной менее трёх букв игнорируются. Если параметром передано два слова через пробел. То каждое должно быть длиной не менее трёх букв.'},\n",
       "  'default': {''}},\n",
       " 'lang': {'description': {'', 'Язык результата: ru или en'},\n",
       "  'default': {'ru'}},\n",
       " 'engine': {'description': {'',\n",
       "   'Показывать типы инструментов для торговой системы.'},\n",
       "  'default': {''}},\n",
       " 'is_trading': {'description': {''}, 'default': {''}},\n",
       " 'market': {'description': {'',\n",
       "   'Рынок\\nEQ - индекс акций\\nFI - индекс облигаций\\nMX - составные индексы\\n'},\n",
       "  'default': {''}},\n",
       " 'group_by': {'description': {'Группировать выводимый результат по полю. Доступны значения group и type.'},\n",
       "  'default': {''}},\n",
       " 'limit': {'description': {'',\n",
       "   'Количество выводимых бумаг (20,100)',\n",
       "   'Количество выводимых бумаг доступны значения (1, 5, 10, 20, 50, 100)',\n",
       "   'Количество выводимых инструментов (5, 10, 20,100)',\n",
       "   'Количество отдаваемых строк.\\nДоступные значения: 100, 50, 20, 10, 5, 1',\n",
       "   'Количество строк в данных. Доступные значения: 1, 10, 100, 1000, 5000',\n",
       "   'Ограничить количество отдаваемых строк.'},\n",
       "  'default': {'', '10', '100', '20', '5000'}},\n",
       " 'group_by_filter': {'description': {'Фильтровать по типам group или type.\\nЗависит от значения фильтра group_by'},\n",
       "  'default': {''}},\n",
       " 'start': {'description': {'',\n",
       "   'Номер строки (отсчет с нуля), с которой следует начать порцию возвращаемых данных (см. рук-во разработчика).\\nПолучение ответа без данных означает, что указанное значение превышает число строк, возвращаемых запросом.',\n",
       "   'Номер строки (отсчет с нуля), с которой следует начать порцию возвращаемых данных (см. рук-во разработчика).\\r\\nПолучение ответа без данных означает, что указанное значение превышает число строк, возвращаемых запросом.',\n",
       "   'Номер строки (отсчет с нуля), с которой следует начать порцию возвращаемых данных (см. рук-во разработчика). Получение ответа без данных означает, что указанное значение превышает число строк, возвращаемых запросом.'},\n",
       "  'default': {'0'}},\n",
       " 'is_tonight_session': {'description': {'Показывать обороты за вечернюю сессию'},\n",
       "  'default': {'0'}},\n",
       " 'date': {'description': {'',\n",
       "   'Дата',\n",
       "   'Дата выплаты НДК',\n",
       "   'Дата за которую необходимо вывести данные. \\nФормат: ГГГГ-ММ-ДД.',\n",
       "   'Дата за которую необходимо вывести данные. \\r\\nФормат: ГГГГ-ММ-ДД.',\n",
       "   'Дата за которую необходимо вывести данные. Формат: ГГГГ-ММ-ДД.',\n",
       "   'Дата формирования бюллетеня',\n",
       "   'Показывать тикеры за конкретную дату',\n",
       "   'дата за которую необходимо отобразить данные.\\nПо умолчанию за последнюю дату в итогах торгов.'},\n",
       "  'default': {'', 'last', 'latest', 'today'}},\n",
       " 'is_traded': {'description': {'Показывать торгуемые только торгующиеся в настоящий момент группы режимов торгов:\\nis_traded=1'},\n",
       "  'default': {'0'}},\n",
       " 'hide_inactive': {'description': {''}, 'default': {'0'}},\n",
       " 'securitygroups': {'description': {''}, 'default': {''}},\n",
       " 'trade_engine': {'description': {''}, 'default': {''}},\n",
       " 'time': {'description': {'',\n",
       "   'Если указано - выдается ближайшее значение КБД к данному моменту времени.'},\n",
       "  'default': {''}},\n",
       " 'asset_type': {'description': {'Фильтр по типу базового актива. (S - Опционы на акцию)',\n",
       "   'Фильтр по типу базового актива. (S - Опционы на акцию, F - Опционы на фьючерс)'},\n",
       "  'default': {'', 'S'}},\n",
       " 'sort_order': {'description': {'',\n",
       "   'No description available.',\n",
       "   'Направление сортировки.\\n- \"asc\" - По возрастанию значения\\n- \"desc\" - По убыванию\\n',\n",
       "   'Поле, по которому сортируется ответ.',\n",
       "   \"Порядок сортировки.\\nДоступные значения 'asc' и 'desc'.\"},\n",
       "  'default': {'TRADEDATE', 'asc', 'emitent'}},\n",
       " 'sort_order_desc': {'description': {\"(Устаревшее)\\nПорядок сортировки.\\nДоступные значения Пусто и 'DESC'.\",\n",
       "   \"(Устарело)\\nПорядок сортировки.\\nДоступные значения Пусто и 'DESC'.\",\n",
       "   \"Порядок сортировки.\\r\\nДоступные значения Пусто и 'DESC'.\"},\n",
       "  'default': {'', 'ASC'}},\n",
       " 'tradingsession': {'description': {'Показать данные только за необходимую сессию\\n1 - Основная\\n2 - Вечерняя\\n3 - Итого',\n",
       "   'Показать данные только за необходимую сессию\\n1 - Основная\\n2 - Вечерняя\\n3 - Итого (по умолчанию)',\n",
       "   'Показать данные только за необходимую сессию (только для фондового рынка)\\n  0 - Утренняя\\n  1 - Основная\\n  2 - Вечерняя\\n  3 - Итого',\n",
       "   'Фильтровать по типу торговой сессии:\\n  1 - Утренняя сессия\\n  1 - Основная сессия\\n  2 - Вечерняя сессия\\n  3 - Суммарно по всем сессиям\\nПо умолчанию показываются все сессии.'},\n",
       "  'default': {'', '3'}},\n",
       " 'security_collection': {'description': {'', 'Фильтр по коллекции'},\n",
       "  'default': {''}},\n",
       " 'type': {'description': {'Тип капитализации. Доступные значения: daily, monthly'},\n",
       "  'default': {'daily'}},\n",
       " 'latest': {'description': {\"Последний срез за день ('1'-включить)\"},\n",
       "  'default': {'0'}},\n",
       " 'table_type': {'description': {''}, 'default': {'full'}},\n",
       " 'only_actual': {'description': {'Выводить индексы в базе которого бумага находится прямо сейчас.'},\n",
       "  'default': {'0'}},\n",
       " 'securities': {'description': {'Фильтр-список инструментов. Получение данных производится только по инстурментам из списка securities.\\nНапример: https://iss.moex.com/iss/engines/stock/markets/shares/securities?securities=GAZP,AFLT,LKOH\\nОграничение: 10 инструментов.',\n",
       "   'Фильтр-список инструментов. Получение данных производится только по инстурментам из списка securities. Например: securities=GAZP,AFLT,LKOH',\n",
       "   'Фильтр-список инструментов. Получение данных производится только по инстурментам из списка securities. Например: securities=GAZP,AFLT,LKOH (не более 10 инструментов).'},\n",
       "  'default': {''}},\n",
       " 'boardid': {'description': {'Отфильтровать выдачу по режиму торгов.\\nНапример: boardid=TQBR,SMAL (не более 10 режимов).'},\n",
       "  'default': {''}},\n",
       " 'from': {'description': {'',\n",
       "   'Дата в формате YYYY-MM-DD начиная с которой отдаются данные.\\n\\nОбратите внимание, что для данного запроса нет постраничной навигации. вам необходимо будет изменять параметр &from на дату следующую после полученных вами данных.\\nКоличество отдаваемых записей в одном блоке ограничено 1000.',\n",
       "   'Дата, начиная с которой необходимо начать выводить данные.\\nФормат: ГГГГ-ММ-ДД.',\n",
       "   'Дата, начиная с которой необходимо начать выводить данные.\\r\\nФормат: ГГГГ-ММ-ДД.',\n",
       "   'Дата, начиная с которой необходимо начать выводить данные. \\nФормат: ГГГГ-ММ-ДД.',\n",
       "   'Дата, начиная с которой необходимо начать выводить данные. Формат: ГГГГ-ММ-ДД.'},\n",
       "  'default': {'', '1997-01-01', '2000-01-01'}},\n",
       " 'till': {'description': {'',\n",
       "   'Дата в формате YYYY-MM-DD которой будет заканчиваться интервал.\\nДанный параметр должен быть больше или равен параметру till.\\n',\n",
       "   'Дата в формате YYYY-MM-DD которой будет заканчиваться интервал.\\r\\nДанный параметр должен быть больше или равен параметру till.\\r\\n',\n",
       "   'Дата, до которой выводить данные.\\nФормат: ГГГГ-ММ-ДД.',\n",
       "   'Дата, до которой выводить данные.\\r\\nФормат: ГГГГ-ММ-ДД.',\n",
       "   'Дата, до которой выводить данные. \\nФормат: ГГГГ-ММ-ДД.',\n",
       "   'Дата, до которой выводить данные. Формат: ГГГГ-ММ-ДД.'},\n",
       "  'default': {'', '2037-12-31', '2100-01-01', '2100-12-31'}},\n",
       " 'show': {'description': {''}, 'default': {''}},\n",
       " 'status': {'description': {'Фильтр торгуемости инструментов: traded, nottraded или all'},\n",
       "  'default': {'all'}},\n",
       " 'numtrades': {'description': {'Минимальное количество сделок с бумагой',\n",
       "   'Минимальное количество сделок с бумагой.',\n",
       "   'Минимальное количество сделок с бумагой.\\n'},\n",
       "  'default': {'0'}},\n",
       " 'interim': {'description': {'Показать интервал дат промежуточных итогов торгов (только для валютного рынка)',\n",
       "   'Показать промежуточные итоги торгов (только для валютного рынка)'},\n",
       "  'default': {'0'}},\n",
       " 'assetcode': {'description': {'Фильтр по коду базового актива. \\nТолько для Фьючерсов и опционов.'},\n",
       "  'default': {''}},\n",
       " 'sort_column': {'description': {'',\n",
       "   'Поле по которому сортируются выдача данных.',\n",
       "   'Поле, по которому сортируется ответ.'},\n",
       "  'default': {'', 'TRADEDATE', 'secid'}},\n",
       " 'security_type_id': {'description': {'Фильтровать по типу бумаг'},\n",
       "  'default': {''}},\n",
       " 'primary_board': {'description': {'Показывать инструменты - только для главного режима торгов по бумаге.\\nФильтр работает только для фондового (stock) и валютного (currency) рынка.\\nБудьте осторожны используя данный фильтр за пределами основных режимов торгов.\\nСписок главных режимов торгов: https://iss.moex.com/iss/securities/ колонка \"primary_boardid\".'},\n",
       "  'default': {''}},\n",
       " 'assets': {'description': {'Фильтр по коду базового актива (только для срочного рынка). Колонка ASSETCODE.\\nПример: https://iss.moex.com/iss/engines/futures/markets/forts/securities?assets=EURRUBTOM,AFLT\\nОграничения: не более 5 типов бумаг.'},\n",
       "  'default': {''}},\n",
       " 'index': {'description': {'Выводить акции из базы индекса.\\nТолько для фондового рынка.'},\n",
       "  'default': {''}},\n",
       " 'previous_session': {'description': {'Данные предыдущей сессии для forts и options рынков',\n",
       "   'Данные предыдущей сессии для forts и options рынков  ',\n",
       "   'Сделки предыдущей сессии для forts и options рынков.\\nИ для индексов.\\n'},\n",
       "  'default': {'0'}},\n",
       " 'first': {'description': {'Отдать n первых строк'}, 'default': {'0'}},\n",
       " 'leaders': {'description': {'Лидеры торгов (только для futures/options и индексов фондового рынка)',\n",
       "   'Лидеры торгов (только для futures/options)'},\n",
       "  'default': {'0'}},\n",
       " 'nearest': {'description': {'Выводить фьючерсы с ближайшей датой погашения'},\n",
       "  'default': {'0'}},\n",
       " 'sectypes': {'description': {'Фильтр по типу инструмента (только для срочного и фондового рынков).\\nНапример:\\n  - https://iss.moex.com/iss/engines/futures/markets/forts/securities?sectypes=ri,gz\\n  - https://iss.moex.com/iss/engines/stock/markets/bonds/securities?sectypes=8\\nОграничения: не более 5 типов бумаг.\\n!! Для срочного рынка устарело. Используйте фильтр \"assets\".'},\n",
       "  'default': {''}},\n",
       " 'seqnum': {'description': {'Номер обновления. Инкрементальный параметр, используется клиентскими приложениями для получения только обновлений данных.'},\n",
       "  'default': {'0'}},\n",
       " 'tradeno': {'description': {'Номер сделки, с которого следует начать возвращать данные.\\nВ данных возвращаются сделки начиная с номера переданной сделки переданого в параметре tradeno.\\nЕсли необходимо начать со следующей сделки - используйте параметр \"next_trade=1\".\\n\\nСделки возвращаются в порядке их заключения. На срочном рынке номер более поздней сделки может быть менее номера предыдущей.\\n'},\n",
       "  'default': {'0'}},\n",
       " 'reversed': {'description': {'Обратный порядок сортировки',\n",
       "   'обратный порядок сортировки'},\n",
       "  'default': {'0'}},\n",
       " 'recno': {'description': {'Только для срочного рынка (FORTS + OPTIONS).\\nФильтрация по полю RECNO (в порядке заключения сделок).\\n\\nЗаменяет собой фильтр tradeno.\\nЕсли необходимо начать со следующей сделки - используйте параметр \"next_trade=1\".\\n'},\n",
       "  'default': {'0'}},\n",
       " 'next_trade': {'description': {'Не включать текущую сделку в выдачу.'},\n",
       "  'default': {'0'}},\n",
       " 'yielddatetype': {'description': {'Тип доходности. Возможные значения\\n- MBS\\n- MATDATE\\n- OFFERDATE'},\n",
       "  'default': {''}},\n",
       " 'interval': {'description': {'Интервал графика.'}, 'default': {'10'}},\n",
       " 'iss.reverse': {'description': {'Изменить порядок сортировки на обратный. Принимает значения true/false.'},\n",
       "  'default': {'false'}},\n",
       " 'year': {'description': {'Год за который показывать календарь',\n",
       "   'Фильтр по году'},\n",
       "  'default': {'', 'today'}},\n",
       " 'month': {'description': {'Фильтр по месяцу'}, 'default': {''}},\n",
       " 'format': {'description': {'На какой формат данных отдавать ссылку: csv,json,xml\\r\\nПо умолчанию отдаётся на файлы в запрашиваемом формате.\\r\\n'},\n",
       "  'default': {''}},\n",
       " 'expiration_date': {'description': {'', 'Дата исполнения контракта'},\n",
       "  'default': {''}},\n",
       " 'option_type': {'description': {\"Тип опциона: 'C' - Call, 'P' - Put.\"},\n",
       "  'default': {'C'}},\n",
       " 'series_type': {'description': {'Тип серии'}, 'default': {''}},\n",
       " 'tickers': {'description': {''}, 'default': {''}}}"
      ]
     },
     "execution_count": 265,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "UNIQUE_KEY"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f3dcb53",
   "metadata": {},
   "source": [
    "Сейчас перейдем к части подготовки чек-функций для доступных параметров"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 299,
   "id": "5521c38b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 1. Выпилим параметры, которые считаем безсполезными\n",
    "UNUSE_PARAMS = {\n",
    "    \"lang\", \"is_tonight_session\", \"sort_order_desc\", \"table_type\", \"show\", \"security_type_id\", \n",
    "    \"seqnum\", \"format\", \"group_by_filter\",\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 275,
   "id": "3e659d5f",
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# Этот поинт генерит ссылки на данные доступные только подписчикам\n",
    "API_PARAMS.pop(116, None)\n",
    "OK_API.pop(116, None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 300,
   "id": "e981cd22",
   "metadata": {},
   "outputs": [],
   "source": [
    "for param in UNUSE_PARAMS:\n",
    "    UNIQUE_KEY.pop(param, None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 301,
   "id": "52804d11",
   "metadata": {},
   "outputs": [],
   "source": [
    "for point in API_PARAMS.values():\n",
    "    for entity in point.values():\n",
    "        params = entity.get(\"params\")\n",
    "        if params is None:\n",
    "            continue\n",
    "        for idx in range(len(params)-1, -1, -1):\n",
    "            if params[idx].key in UNUSE_PARAMS:\n",
    "                params.pop(idx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 389,
   "id": "4948c3c9",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'description': {''}, 'default': {''}}"
      ]
     },
     "execution_count": 389,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "UNIQUE_KEY['tickers']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 391,
   "id": "1e4e72aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Напишем миксин проверки \n",
    "# Для поиска значения в множестве можно создать метод - генерирующий функцию проверки (аля DRY), но для удобства \n",
    "# сопровождения будем писать отдельный метод под каждое множество.\n",
    "\n",
    "ENDPOINT_DEFAULTS = {\n",
    "    873: {\"limit\": 500},\n",
    "    791: {\"numtrades\": 1},\n",
    "    147: {\"limit\": 100},\n",
    "}\n",
    "\n",
    "ALLOWED_MANY = {\"securities\", \"boardid\", \"assets\", \"sectypes\"}\n",
    "\n",
    "\n",
    "CHECK_METHODS = {\n",
    "    'q': \"check_instrument_find\",\n",
    "    'engine': \"check_engine\",\n",
    "    'is_trading': \"check_bool\",\n",
    "    'market': \"check_market\",\n",
    "    'group_by': \"check_group_by\",\n",
    "    'limit': \"check_limit\",\n",
    "    'start': \"check_start\",\n",
    "    'date': \"check_date\",\n",
    "    'is_traded': \"check_bool\",\n",
    "    'hide_inactive': \"check_bool\",\n",
    "    'securitygroups': \"check_security_group\",\n",
    "    'trade_engine': \"check_engine\",\n",
    "    'time': \"check_time\",\n",
    "    'asset_type': \"check_asset_type\",\n",
    "    'sort_order': \"check_sort_order\",\n",
    "    'tradingsession': \"check_session\",\n",
    "    'security_collection': \"check_security_collection\",\n",
    "    'type': \"check_type\",\n",
    "    'latest': \"check_bool\",\n",
    "    'only_actual': \"check_bool\",\n",
    "    'securities': \"check_securities\",\n",
    "    'boardid': \"check_boards\",\n",
    "    'from': \"check_date\",\n",
    "    'till': \"check_date\",\n",
    "    'status': \"check_status\",\n",
    "    'numtrades': \"check_int\",\n",
    "    'interim': \"check_bool\",\n",
    "    'assetcode': \"check_instrument_find\",\n",
    "    'sort_column': \"check_sort_columns\",\n",
    "    'primary_board': \"check_bool\",\n",
    "    'assets': \"check_assets\",\n",
    "    'index': \"check_bool\",\n",
    "    'previous_session': \"check_bool\",\n",
    "    'first': \"check_int\",\n",
    "    'leaders': \"check_bool\",\n",
    "    'nearest': \"check_bool\",\n",
    "    'sectypes': \"check_sectype\",\n",
    "    'tradeno': \"check_int\",\n",
    "    'reversed': \"check_bool\",\n",
    "    'recno': \"check_bool\",\n",
    "    'next_trade': \"check_bool\",\n",
    "    'yielddatetype': \"check_yielddatetype\",\n",
    "    'interval': \"check_interval\",\n",
    "    'iss.reverse': \"check_bool_like_bool\",\n",
    "    'year': \"check_int\",\n",
    "    'month': \"check_int\",\n",
    "    'expiration_date': \"check_date\",\n",
    "    'option_type': \"check_option_type\",\n",
    "    'series_type': \"check_option_series_type\",\n",
    "    'tickers': \"check_instrument_find\",\n",
    "}\n",
    "\n",
    "class MoexParamCheckerMixin:\n",
    "    SECTYPE = {\n",
    "        '1': 'Акция обыкновенная',\n",
    "        '2': 'Акция привилегированная',\n",
    "        '3': 'Государственные облигации',\n",
    "        '4': 'Региональные облигации',\n",
    "        '5': 'Облигации центральных банков',\n",
    "        '6': 'Корпоративные облигации',\n",
    "        '7': 'Облигации МФО',\n",
    "        '8': 'Биржевые облигации',\n",
    "        '9': 'Паи открытых ПИФов',\n",
    "        'A': 'Паи интервальных ПИФов',\n",
    "        'B': 'Паи закрытых ПИФов',\n",
    "        'C': 'Муниципальные облигации',\n",
    "        'D': 'Депозитарные расписки',\n",
    "        'E': 'Бумаги иностранных инвестиционных фондов (ETF)',\n",
    "        'F': 'Ипотечный сертификат',\n",
    "        'G': 'Корзина бумаг',\n",
    "        'H': 'Доп. идентификатор списка',\n",
    "        'I': 'ETC (товарные инструменты)',\n",
    "        'J': 'Пай биржевого ПИФа (Exchange Investment Unit share)'\n",
    "    }\n",
    "    OPTION_SERIES_TYPE = {\n",
    "        \"D\": \"дневной\",\n",
    "        \"W\": \"недельный\",\n",
    "        \"M\": \"месячный\", \n",
    "        \"Q\": \"квартальный\",\n",
    "    }\n",
    "    \n",
    "    current_endpoint_id: int = -1\n",
    "    \n",
    "    # Добавить в основной класс\n",
    "    set_engine: set = set()\n",
    "    set_security_group: set = set()\n",
    "    set_session: set[int] = set()\n",
    "    set_security_collection: set = set()\n",
    "    set_board: set = set()\n",
    "    set_security_type: set = set()\n",
    "    set_duration: set[int] = set()\n",
    "        \n",
    "    set_market: set = {\"EQ\", \"FI\", \"MX\"}\n",
    "    set_group_by: set = {\"group\", \"type\"}\n",
    "    set_asset_type: set = {\"S\", \"F\"}\n",
    "    set_sort_order: set = {\"asc\", \"desc\"}\n",
    "    set_type: set = {\"daily\", \"monthly\"}\n",
    "    set_status = {\"traded\", \"nottraded\", \"all\"}\n",
    "    set_yielddatetype = {\"MBS\", \"MATDATE\", \"OFFERDATE\"}\n",
    "    set_option_type = {\"C\", \"P\"}\n",
    "\n",
    "    \n",
    "    @abstractmethod\n",
    "    def get_endpoint_columns(self) -> set: \n",
    "        \"\"\"\n",
    "        У многих эндпоинтов есть сортировка по столбцу. Столбцы мы уже получили в json формате ранее.\n",
    "        Подготовим их чуть позже и сохраним в отдельном модуле.\n",
    "        \"\"\"\n",
    "\n",
    "    @staticmethod\n",
    "    def check_instrument_find(q: Any):\n",
    "        if not isinstance(q, str):\n",
    "            q = str(q)\n",
    "        for word in q.split():\n",
    "            if len(word) < 3:\n",
    "                raise ValueError(\"Запрос инструментов длиной менее трёх букв игнорируются.\")\n",
    "        return q\n",
    "                \n",
    "    def check_engine(self, engine: str):\n",
    "        if engine not in self.set_engine:\n",
    "            raise ValueError(f\"Engine: '{engine}' не найден\")\n",
    "        return engine\n",
    "    \n",
    "    @staticmethod\n",
    "    def check_bool(val: Any):\n",
    "        if val:\n",
    "            return 1\n",
    "        return 0\n",
    "    \n",
    "    def check_market(self, market: str):\n",
    "        upper_market = market.upper()\n",
    "        if upper_market not in self.set_market:\n",
    "            raise ValueError(\"Доступны: EQ - индекс акций, FI - индекс облигаций, MX - составные индексы\")\n",
    "        return upper_market\n",
    "    \n",
    "    def check_group_by(self, group_by: str):\n",
    "        lower_group_by = group_by.lower()\n",
    "        if lower_group_by not in self.set_group_by:\n",
    "            raise ValueError(\"Доступны значения group и type\")\n",
    "        return lower_group_by\n",
    "    \n",
    "    @staticmethod\n",
    "    def check_limit(self, limit: int):\n",
    "        if not isinstance(limit, int):\n",
    "            raise ValueError(\"Лимит должен быть целым числом\")\n",
    "        return limit\n",
    "    \n",
    "    @staticmethod\n",
    "    def check_start(self, start: int):\n",
    "        if not isinstance(limit, int):\n",
    "            raise ValueError(\"Курсор должен быть целым числом\")\n",
    "        return start\n",
    "    \n",
    "    @staticmethod\n",
    "    def check_date(value_date: Union[str, datetime.date, datetime.datetime]):\n",
    "        if isinstance(value_date, (datetime.date, datetime.datetime)):\n",
    "            return value_date.strftime(value_date, '%Y-%m-%d')\n",
    "        try:\n",
    "            datetime.datetime.strptime(value_date, '%Y-%m-%d')\n",
    "            return value_date\n",
    "        except:\n",
    "            raise ValueError(f\"Значение даты {value_date} не является датой или не удовлетворяет формату ГГГГ-ММ-ДД\")\n",
    "            \n",
    "    def check_security_group(self, security_group: str):\n",
    "        if security_group not in self.set_security_group:\n",
    "            raise ValueError(f\"Группа {security_group} не найдена.\")\n",
    "        return security_group\n",
    "    \n",
    "    @staticmethod\n",
    "    def check_time(value_time: Union[str, datetime.time, datetime.datetime]):\n",
    "        if isinstance(value_time, (datetime.time, datetime.datetime)):\n",
    "            return value_time.strftime(value_time, '%H:%M:%S')\n",
    "        try:\n",
    "            datetime.datetime.strptime(value_time, '%H:%M:%S')\n",
    "            return value_time\n",
    "        except:\n",
    "            raise ValueError(f\"Значение времени {value_time} не удовлетворяет формату ЧЧ:ММ:CC\")\n",
    "    \n",
    "    def check_asset_type(self, asset_type: str):\n",
    "        upper_asset_type = asset_type.upper()\n",
    "        if upper_asset_type not in self.set_asset_type:\n",
    "            raise ValueError(\"Доступны фильтры по типу базового актива. (S - Опционы на акцию, F - Опционы на фьючерс)\")\n",
    "        return upper_asset_type\n",
    "    \n",
    "    \n",
    "    def check_sort_order(self, sort_order: str):\n",
    "        lower_sort_order = sort_order.lower()\n",
    "        if lower_sort_order not in self.set_sort_order:\n",
    "            raise ValueError('Направление сортировки. \"asc\" - По возрастанию значения, \"desc\" - По убыванию!')\n",
    "        return lower_sort_order\n",
    "    \n",
    "    def check_session(self, session: Union[str, int]):\n",
    "        if isinstance(session, str):\n",
    "            if session.isdigit():\n",
    "                session = int(session)\n",
    "        if session in self.set_session:\n",
    "            return session\n",
    "        raise ValueError('Укажите корректную сессию: 0 - Утренняя;  1 - Основная;  2 - Вечерняя;  3 - Итого')\n",
    "        \n",
    "    def check_security_collection(self, security_collection: str):\n",
    "        if security_collection not in self.set_security_collection:\n",
    "            raise ValueError(f\"Группа ФИ '{security_collection}' не найдена.\")\n",
    "        return security_collection\n",
    "    \n",
    "    def check_type(self, value_type: str):\n",
    "        lower_value_type = value_type.lower()\n",
    "        if lower_value_type not in self.set_type:\n",
    "            raise ValueError(\"Не верный тип капитализации. Доступные значения: daily, monthly\")\n",
    "        return lower_value_type\n",
    "    \n",
    "    @staticmethod\n",
    "    def check_securities(securities: Union[str, List[str]], max_security=10):\n",
    "        if isinstance(securities, list) and len(securities) > max_security:\n",
    "            raise ValueError(f\"Запросить можно не более {max_security} фин. инструментов\")\n",
    "        return securities\n",
    "    \n",
    "    def check_boards(self, boards: Union[str, List[str]]):\n",
    "        if isinstance(boards, str):\n",
    "            boards = [boards]\n",
    "        for board in boards:\n",
    "            if board not in self.set_board:\n",
    "                raise ValueError(f\"Площадка '{board}' не найдена.\")\n",
    "        return boards\n",
    "    \n",
    "    def check_status(self, status: str):\n",
    "        lower_status = status.lower()\n",
    "        if lower_status not in self.set_status:\n",
    "            raise ValueError(f\"Ошибка cтатуса. Укажите фильтр торгуемости инструментов: traded, nottraded или all\")\n",
    "        return lower_status\n",
    "    \n",
    "    @staticmethod\n",
    "    def check_int(value: Union[str, int]):\n",
    "        if isinstance(value, int) or (isinstance(value, str) and value.isdigit()):\n",
    "            return value\n",
    "        raise ValueError(f\"Передан признак [{value}], который должен быть целым числом!!! \")\n",
    "        \n",
    "    def check_sort_columns(self, column):\n",
    "        available_columns: set = self.get_endpoint_columns()\n",
    "        if column not in available_columns:\n",
    "            raise ValueError(f\"Не найден атрибут сортировки {column} в доступных {sorted(available_columns)}\")\n",
    "        return column\n",
    "    \n",
    "    def check_assets(self, securities: Union[str, List[str]]):\n",
    "        return self.check_securities(securities, 5)\n",
    "    \n",
    "    def check_sectype(self, values: Union[str, List[str]]):\n",
    "        warnings.warn(\n",
    "            \"\"\"\n",
    "            Поле 'sectypes' не соответствует значениям из глобального справочника. Для ПФИ указывается краткий код БА,\n",
    "            например, si, ri, mx и т.д. Для спота обратитесть к справочнику 'SECTYPE' объекта.\n",
    "            \"\"\"\n",
    "        )\n",
    "        if isinstance(values, list) and len(values) > 5:\n",
    "            raise ValueError(f\"Запросить можно не более 5 типов фин. инструментов\")\n",
    "        elif isinstance(values, str):\n",
    "            values = [values]\n",
    "        for value in values:\n",
    "            if len(value) > 1:\n",
    "                continue\n",
    "            if value not in self.SECTYPE:\n",
    "                raise ValueError(f\"Код {value} не найден в справочнике типов фин. инструментов\")\n",
    "        return values\n",
    "        \n",
    "    def check_yielddatetype(self, yielddatetype: str):\n",
    "        upper_yielddatetype = yielddatetype.upper()\n",
    "        if upper_yielddatetype not in self.set_yielddatetype:\n",
    "            raise ValueError(f\"Фильтр доступен по типам доходности: MBS, MATDATE, OFFERDATE\")\n",
    "        return upper_yielddatetype\n",
    "    \n",
    "    def check_interval(self, interval: int):\n",
    "        if interval not in self.set_duration:\n",
    "            raise ValueError(\"Интервал должен соответствовать доступным значениям. Смотрите справочник 'durations'.\")\n",
    "        return interval\n",
    "    \n",
    "    def check_bool_like_bool(self, value: Any):\n",
    "        if self.check_bool(value):\n",
    "            return \"true\"\n",
    "        return \"false\"\n",
    "    \n",
    "    def check_option_type(self, option_type: str):\n",
    "        upper_option_type = option_type.upper()\n",
    "        if upper_option_type not in self.set_option_type:\n",
    "            raise ValueError(\"Не верный тип опциона. C - CALL, P - PUT\")\n",
    "        return upper_option_type\n",
    "    \n",
    "    def check_option_series_type(self, series_type: str):\n",
    "        upper_series_type = series_type.upper()\n",
    "        if upper_series_type not in self.OPTION_SERIES_TYPE:\n",
    "            raise ValueError(\"Не найдена серия опциона. Обратитесь к справочнику OPTION_SERIES_TYPE\")\n",
    "        return upper_series_type\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf792e64",
   "metadata": {},
   "source": [
    "#### Миксин готов. Соберем поля, возвращаемые каждым поинтом:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 396,
   "id": "87ef4ed3",
   "metadata": {},
   "outputs": [],
   "source": [
    "all_columns = {}\n",
    "path = \"moex_api_response\"\n",
    "\n",
    "def join_columns(api_id):\n",
    "    all_columns[api_id] = d = {}\n",
    "    with open(f\"{path}/{api_id}.json\", \"r\") as file:\n",
    "        data = json.load(file)\n",
    "    for key, value in data.items():\n",
    "        d[key] = value[\"columns\"]\n",
    "\n",
    "        \n",
    "for key in OK_API.keys():\n",
    "    join_columns(key)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 398,
   "id": "abd77e74",
   "metadata": {},
   "outputs": [],
   "source": [
    "# В указанных поинтах косяки, не определны колонки. Остальное ок.\n",
    "all_columns[769]['netflow2'] = []\n",
    "all_columns[809]['futoi'] = []"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "03a0385d",
   "metadata": {},
   "source": [
    "Часть эндпоинтов возвращает курсор, а чать - нет, хотя и в них присутствует пагинация. Для нашей либы нужно знать, что это за эндпроинты.\n",
    "Дополнительно проверим атрибутный состав курсоров."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 402,
   "id": "e377f239",
   "metadata": {},
   "outputs": [],
   "source": [
    "api_cursor = {}\n",
    "cursor_columns = set()\n",
    "for api_id, api_resp in all_columns.items():\n",
    "    for entity in api_resp:\n",
    "        if entity.find('cursor') != -1:\n",
    "            api_cursor[api_id] = entity\n",
    "            cursor_columns.add(tuple(api_resp[entity]))\n",
    "            break\n",
    "    if api_id in api_cursor:\n",
    "        continue\n",
    "        \n",
    "    entites_params = API_PARAMS[api_id]\n",
    "    for values_params in entites_params.values():\n",
    "        params = values_params.get('params', [])\n",
    "        for param in params:\n",
    "            if param.key in {'limit', 'start'}:\n",
    "                api_cursor[api_id] = None\n",
    "                break\n",
    "        if api_id in api_cursor:\n",
    "            break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 403,
   "id": "c291318b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{5: None,\n",
       " 873: None,\n",
       " 172: 'coefficients.cursor',\n",
       " 165: 'data.cursor',\n",
       " 134: 'securities.cursor',\n",
       " 649: None,\n",
       " 191: 'sitenews.cursor',\n",
       " 193: 'events.cursor',\n",
       " 162: 'securities.cursor',\n",
       " 13: None,\n",
       " 89: None,\n",
       " 118: None,\n",
       " 119: None,\n",
       " 813: 'history.cursor',\n",
       " 817: 'history.cursor',\n",
       " 821: 'history.cursor',\n",
       " 815: None,\n",
       " 55: None,\n",
       " 35: None,\n",
       " 56: None,\n",
       " 155: None,\n",
       " 157: None,\n",
       " 46: None,\n",
       " 34: None,\n",
       " 62: 'history.cursor',\n",
       " 791: None,\n",
       " 63: 'history.cursor',\n",
       " 793: None,\n",
       " 64: 'history.cursor',\n",
       " 795: None,\n",
       " 65: None,\n",
       " 797: None,\n",
       " 131: 'securities.cursor',\n",
       " 147: 'analytics.cursor',\n",
       " 715: 'history.cursor',\n",
       " 712: 'securities.cursor'}"
      ]
     },
     "execution_count": 403,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "api_cursor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 404,
   "id": "0482c718",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{('INDEX', 'TOTAL', 'PAGESIZE'),\n",
       " ('INDEX', 'TOTAL', 'PAGESIZE', 'PREV_DATE', 'NEXT_DATE')}"
      ]
     },
     "execution_count": 404,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cursor_columns"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d2a527f",
   "metadata": {},
   "source": [
    "Мы собрали список ключей курсоров (для контролируемых запросов). И поинты по которым курсор недоступен."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 418,
   "id": "e1fff70d",
   "metadata": {},
   "outputs": [],
   "source": [
    "for key, val in OK_API.items():\n",
    "    if not val.description:\n",
    "        print(key, val)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 417,
   "id": "a0f00e12",
   "metadata": {},
   "outputs": [],
   "source": [
    "OK_API[205].description = 'Описание колонок бумаг входящих в индекс'\n",
    "OK_API[165].description = 'РЕПО ГЦБ объемы (УСТАРЕЛО)'\n",
    "OK_API[166].description = 'РЕПО ГЦБ детали (УСТАРЕЛО)'\n",
    "OK_API[196].description = 'Описание колонок по агрегированным показетелям рынка бондов'\n",
    "OK_API[178].description = 'Ставки РЕПО аукционов (ГЦБ)'\n",
    "OK_API[179].description = 'Описание полей для запроса ставок РЕПО аукционов (ГЦБ)'\n",
    "OK_API[767].description = 'Сводная статистика по клиентам с самым высоким нетто-потоком. Детали https://fs.moex.com/f/10374/netflow2-demo-en.html'\n",
    "OK_API[807].description = 'Сводные остатки по фьючерсам на физ. и юр. лица.'\n",
    "OK_API[634].description = 'Кривые безкупонной доходности'\n",
    "OK_API[156].description = 'Интервалы свечей, доступных к запросу по ФИ'\n",
    "OK_API[769].description = (\n",
    "    'Сводная статистика по клиентам с самым высоким нетто-потоком. Запрос возможен с лагом -15 дней от текущего для 1 эшелона.'\n",
    "    'Детали https://fs.moex.com/f/10374/netflow2-demo-en.html'\n",
    ")\n",
    "OK_API[809].description = 'Сводные остатки по фьючерсам на физ. и юр. лица. Запрос возможен с лагом -15 дней, по коду фьючерса'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "529eaf4e",
   "metadata": {},
   "source": [
    "В данном разделе осталось последнее - создать общий справочник для нашей либы. Предлагаю сделать в универсальном формате - JSON. Он должен содержать всю инфу, требуемую для предоставления пользователю описаний api и метаинформации для формирования запросов.\n",
    "Что он должен содержать:\n",
    "1. id API\n",
    "2. Описание эндпоинта\n",
    "3. url FAQ\n",
    "4. Шаблон API\n",
    "5. Глобальные ключи сущностей\n",
    "6. Список возвращаемых API данных и их описание\n",
    "7. Колонки возвращаемых данных\n",
    "8. Ключи параметров запроса, их описание и дефолтное значение\n",
    "9. Наличие курсоров"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b8d86f4",
   "metadata": {},
   "source": [
    "**Пример структуры:**\n",
    "```\n",
    "$id: {\n",
    "    description: str \n",
    "    faq_url: str\n",
    "    endpoint: str\n",
    "    global_entities: list[$entity_name] | None\n",
    "    has_cursor: bool | None\n",
    "    cursor_name: str | None\n",
    "    return_data: {\n",
    "        $data_name: {\n",
    "            description: str\n",
    "            columns: list[str]\n",
    "        }\n",
    "    }\n",
    "    params: {\n",
    "        $key_name: {\n",
    "            description: str\n",
    "            default: str | None\n",
    "        }\n",
    "    }\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 432,
   "id": "c5997d9f",
   "metadata": {},
   "outputs": [],
   "source": [
    "FINAL_JSON = {}\n",
    "for key, value in OK_API.items():\n",
    "    FINAL_JSON[key] = d = {}\n",
    "    d['description'] = value.description\n",
    "    d['faq_url'] = value.url_faq\n",
    "    d['endpoint'] = value.url_template\n",
    "    global_entities = [entity[1:-1] for entity in re.findall(r'{\\w*}', value.url_template)]\n",
    "    if global_entities:\n",
    "        d['global_entities'] = global_entities\n",
    "    if key in api_cursor:\n",
    "        d['has_cursor'] = True\n",
    "        d['cursor_name'] = api_cursor[key]\n",
    "    api_params = API_PARAMS[key]\n",
    "    params = dict()\n",
    "    return_data = dict()\n",
    "    for data_entity, data_value in api_params.items():\n",
    "        return_data[data_entity] = {\n",
    "            \"description\": data_value.get('description', ''),\n",
    "            \"columns\": all_columns[key].get(data_entity, []),\n",
    "        }\n",
    "        for param_key in data_value.get('params', []):\n",
    "            if param_key.key not in params:\n",
    "                params[param_key.key] = {\n",
    "                    \"description\": param_key.description,\n",
    "                    \"default\": param_key.default,\n",
    "                }\n",
    "                \n",
    "    d['return_data'] = return_data\n",
    "    d['params'] = params\n",
    "        \n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 438,
   "id": "d27b5ec4",
   "metadata": {},
   "outputs": [],
   "source": [
    "with open(\"MOEX_API_DICT.json\", \"w\", encoding='utf-8') as file:\n",
    "    json.dump(FINAL_JSON, file, ensure_ascii=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e0b408e",
   "metadata": {},
   "source": [
    "#### На этом сбор данных об апи закончен. Мы собрали нужные данные для разработки библиотеки быстрой работы с API MOEX. Идеи разработки ниже. В следующей части мы протестим уже ее работу.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29f0fca8",
   "metadata": {},
   "source": [
    "1. Заведение общих справочников\n",
    "2. Заведение глобальных сущностей\n",
    "3. Подготовка методов поиска ФИ и торгуемых площадок\n",
    "4. Инструмент для работы с сущностями\n",
    "5. Возрват доступных АПИ и их описание\n",
    "6. Запросы по апи с параметрами\n",
    "7. Проверки параметров при запросе\n",
    "8. Запросы с курсорами\n",
    "9. Запросы с лимитами без курсоров\n",
    "10. Обновление справочника\n",
    "\n",
    "Писать буду в IDE (Pycharm)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
